package com.yc.spring;

import lombok.Data;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class MyAnnotationConfigApplicationContext {
    Class configClass;
    Object configObject;

    Map<String, BeanBefinition> beanDefinitionMap=new HashMap<>();
    Map<String, Object> singletonObjects=new HashMap<>();

    public static void main(String[] args) throws Exception {
        MyAnnotationConfigApplicationContext cxt;
        cxt=new MyAnnotationConfigApplicationContext(AutoWriteAnnoConfig.class);
    }
    public Object getBean(String id){
        System.out.println("singletonObjects.containsKey(id) = " + singletonObjects.containsKey(id));
        Object bean = singletonObjects.get(id);
        if(bean == null){
            throw new RuntimeException("没有该bean: " + id);
        }
        return bean;
    }

    public <T> T getBean(Class<T> tClass){
        // stream 编程
        List<Map.Entry> list = singletonObjects.entrySet()
                .stream()
                .filter(entry -> entry.getValue().getClass().isAssignableFrom(tClass))
                .collect(Collectors.toList());
        if(list.isEmpty()){
            throw new RuntimeException("没有该bean: " + tClass);
        } else if(list.size() == 1){
            return (T) list.get(0).getValue();
        } else {
            List<Map.Entry> list1 = list.stream()
                    .filter(entry -> {
                        BeanBefinition bd = beanDefinitionMap.get(entry.getKey());
                        return bd.isPrimary();
                    })
                    .collect(Collectors.toList());
            if(list1.isEmpty()){
                throw new RuntimeException("该bean没有定义primary bean: " + tClass);
            } else if (list1.size() == 1){
                return (T) list1.get(0).getValue();
            } else {
                throw new RuntimeException("该bean定义了多个主primary bean: " + tClass);
            }
        }

    }

    private void createBean() throws Exception {
        for (Map.Entry<String, BeanBefinition> entry : beanDefinitionMap.entrySet()) {
            String id = entry.getKey();
            BeanBefinition bd = entry.getValue();
            if(bd.beanMethod != null){
                // 仅执行五参数的 bean 方法
                if(bd.beanMethod.getParameterCount() == 0){
                    bd.beanMethod.setAccessible(true);
                    Object bean = bd.beanMethod.invoke(configObject);
                    singletonObjects.put(id, bean);
                }
            } else {
                Object bean = bd.beanClass.newInstance();
                singletonObjects.put(id, bean);
            }
        }
    }
    public MyAnnotationConfigApplicationContext(Class configClass) throws Exception {
        this.configClass=configClass;
        configObject=configClass.newInstance();

        //读取配置（基于注解） 创建BeanBefinition对象，存入BeanBefinitionMap容器
        addBeanDefinitionFormMethod();
        addBeanDefinitionFormPackage();
        beanDefinitionMap.forEach((id,bean)->{
            System.out.printf("%s:%s",id,bean);
            System.out.println("\n");
        });
        createBean();
    }

    private void addBeanDefinitionFormPackage() throws ClassNotFoundException {
        ComponentScan componentScan=(ComponentScan)configClass.getAnnotation(ComponentScan.class);
        if(componentScan!=null){
            String[] basePackages=componentScan.value();
            URL url=configClass.getClassLoader().getResource("");
            //  编译目录路径
            String classesPath= URLDecoder.decode(url.getPath());
            //扫描的根路径
            String scanRootPath;
            List<File> classFileList=new ArrayList<>();
            File classesFile=new File(classesPath);
            for(String basePackage:basePackages){
                System.out.println("basePackage = " + basePackage);
                scanRootPath=classesPath+basePackage.replaceAll("\\.","/");
                scanClassFile(new File(scanRootPath),classFileList);
            }
            for (File file : classFileList) {
                String classPath;
                classPath=file.getAbsolutePath().replace(classesFile.getAbsolutePath(),"");
                classPath=classPath.replace(".class","");
                classPath=classPath.substring(1);
                classPath=classPath.replaceAll("/",".");
                System.out.println("replace classPath = " + classPath);

                Class beanClass=Class.forName(classPath);
                Service service=(Service)beanClass.getAnnotation(Service.class);
                Repository repository= (Repository) beanClass.getAnnotation(Repository.class);
                Controller controller= (Controller) beanClass.getAnnotation(Controller.class);
                Component component= (Component) beanClass.getAnnotation(Component.class);
                if(service==null&&repository==null&&controller==null&&component==null){
                    continue;
                }
                //构建BeanBefinition
                BeanBefinition beanBefinition=new BeanBefinition();
                beanBefinition.beanClass=beanClass;
                beanBefinition.lazy=beanClass.getAnnotation(Lazy.class)!=null;
                beanBefinition.primary=beanClass.getAnnotation(Primary.class)!=null;
                if(beanClass.getAnnotation(Scope.class)!=null){
                    beanBefinition.scope=((Scope)beanClass.getAnnotation(Scope.class)).value();
                }
                if(service!=null&&service.value().isBlank()==false){
                    beanBefinition.id=service.value();
                }else if(repository!=null&&repository.value().isBlank()==false){
                    beanBefinition.id=repository.value();
                }else if(controller!=null&&controller.value().isBlank()==false){
                    beanBefinition.id=controller.value();
                }else if(component!=null&&component.value().isBlank()==false){
                    beanBefinition.id=component.value();
                }else{
                    String id=beanClass.getSimpleName();
                    //实现类名首字母小写
                    id=id.substring(0,1).toLowerCase()+id.substring(1);
                    beanBefinition.id=id;
                }
                beanDefinitionMap.put(beanBefinition.id,beanBefinition);
            }
        }
    }
    ///Users/xiaoktongxue/sanqistudy/three-phase-learning/spring/target/classes/com/yc/spring
    private void  scanClassFile(File dir, List<File> classFileList){
        File[] classFiles=dir.listFiles(file->file.getName().endsWith(".class"));
        for(File file:classFiles){
            classFileList.add(file);
        }
        File[] subDirs=dir.listFiles(file->file.isDirectory());
        for(File subDir:subDirs){
            scanClassFile(subDir,classFileList);
        }
    }
    private void addBeanDefinitionFormMethod() {
        Method[] declaredMethods = configClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            BeanBefinition bd=new BeanBefinition();
            bd.beanMethod=method;
            bd.beanClass=method.getReturnType();
            bd.lazy=method.getAnnotation(Lazy.class)!=null;
            bd.primary=method.getAnnotation(Primary.class)!=null;
            Bean beanAnno=method.getAnnotation(Bean.class);
            if(beanAnno.value().length>0){
                bd.id=beanAnno.value()[0];
            }else{
                bd.id=method.getName();
            }
            if(method.getAnnotation(Scope.class)!=null){
                bd.scope=method.getAnnotation(Scope.class).value();
            }
            beanDefinitionMap.put(bd.id,bd);
        }
    }
}

@Data
class BeanBefinition{
    String id;
    Class beanClass;
    Method beanMethod;
    boolean lazy;
    String scope;
    boolean primary;
    //拓展其他属性
}
